﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Xml;
using System.Configuration;
using Gollie.Administration.Service.Tasks;
using Gollie.Administration.Service.Tasks.InvoiceHelpers;
using Gollie.Invoice;
using Gollie.Mail;
using Gollie.Pdf;
using iTextSharp;
using iTextSharp.text;
using iTextSharp.text.pdf;
using MySQL.Db;
using NLog;

namespace Gollie.Administration.Service.Tasks
{
	public class MonthClose : IAdministrationTask
	{
		private static readonly Logger logger = LogManager.GetCurrentClassLogger();

		CancellationToken ct;

		TaskProperties taskProperties;

		public void Run(CancellationToken ct, TaskProperties properties)
		{
			this.ct = ct;

			this.taskProperties = properties;

			this.CloseMonth();
		}

		private void CloseMonth()
		{
			DateTime period = DateTime.Today.AddMonths(-1);

			int month = period.Month;
			int year = period.Year;

			logger.Info("Writing totals per vendor...");
			InvoiceCalculatorCollection invoiceCalculators = new InvoiceCalculatorCollection(month, year);

			Queries.CloseMonth(invoiceCalculators, month, year);
			logger.Info("Done writing totals per vendor.");

			this.GenerateInvoices(invoiceCalculators, month, year);
		}

		private void GenerateInvoices(InvoiceCalculatorCollection invoiceCalculators, int month, int year)
		{
			logger.Info("Generating {0} invoices...", invoiceCalculators.Count);

			EMailCollection emails = new EMailCollection();

			string period = new DateTime(year, month, 1).ToString("MMMM yyyy");

			foreach (KeyValuePair<uint, InvoiceCalculator> kvp in invoiceCalculators)
			{
				ResultSet resultSet = Queries.GetVendor(kvp.Key);

				if (resultSet.Count == 0)
				{
					logger.Error("Could not find vendorid {0}. Continuing...", kvp.Key);
					continue;
				}

				try
				{
					string invoicePath = this.GenerateInvoice(kvp.Key, kvp.Value, resultSet, month, year);

					EMail email = new EMail
					{
						AttachmentPath = invoicePath,
						Name = "pdfinvoice",
						Recipients = resultSet.GetString(0, "billingemail"),
						Args = new Dictionary<string, string>()
						{
							{"{period}", period },
							{"{amount}", (kvp.Value.NetFee + kvp.Value.TotalVat).ToString("c2") },
							{"{aanhef}", "Geachte relatie,<br/><br/>" }
						}
					};

					emails.Add(email);
				}
				catch (Exception e)
				{
					logger.ErrorException("Failed to generate invoice", e);
				}
			}

			logger.Info("Done generating invoices.");

			logger.Info("Sending {0} invoice mails...", emails.Count);
			MailClient.Queue(emails);
		}

		private string GenerateInvoice(uint vendorId, InvoiceCalculator invoiceCalculator, ResultSet vendorResultSet, int month, int year)
		{
			string invoicePath = null;
			string factNr = null;
			try
			{
				logger.Info("Generating invoice for vendor {0}...", vendorId);

				invoicePath = this.GetInvoicePath(vendorId, month, year);

				factNr = Queries.InsertInvoice(vendorId, month, year, invoicePath, invoiceCalculator.NetFee, invoiceCalculator.TotalVat);

				string invoiceTemplatePath = ConfigurationManager.AppSettings["invoicetemplate"];

				using (PdfDocumentCanvas pdfCanvas = new PdfDocumentCanvas(invoicePath, invoiceTemplatePath))
				{

					WriteVendorAddress(pdfCanvas, vendorResultSet);

					WriteInvoiceDetails(pdfCanvas, vendorResultSet, invoiceCalculator);

					WriteInvoiceProperties(pdfCanvas, factNr, month, year);
				}
				
				logger.Info("Done generating invoice for vendor {0}.", vendorId);
			}
			catch (Exception e)
			{
				if (factNr != null)
					Queries.DeleteInvoice(factNr);

				throw new Exception("Generate invoice failed", e);
			}

			return invoicePath;
		}

		private string GetInvoicePath(uint vendorId, int month, int year)
		{
			DateTime period = new DateTime(year, month, 1);

			string invoiceTargetFolder = ConfigurationManager.AppSettings["invoicetarget"];

			string folder = Path.Combine(invoiceTargetFolder, vendorId.ToString("00000000"));

			if (Directory.Exists(folder) == false)
				Directory.CreateDirectory(folder);

			//use filecount as part of filename
			int fileCount = Directory.GetFiles(folder).Length;

			fileCount++;

			string invoiceName = "Factuur_" + vendorId.ToString() + "_" + period.ToString("yyyy-MM") + "_" + fileCount.ToString() + ".pdf";

			string invoicePath = Path.Combine(folder, invoiceName);

			return invoicePath;
		}

		private void WriteVendorAddress(PdfDocumentCanvas pdfCanvas, ResultSet vendor)
		{
			PdfElementProperties properties = GetProps("vendorinfotable");

			PdfTable addressTable = new PdfTable(properties);
			PdfCellProperties cellProps = new PdfCellProperties { PaddingBottom = 0f, PaddingTop = 2f };
			cellProps.Font = FontFactory.GetFont(properties.FontFamily, properties.FontSize, properties.FontWeight);
			string[] vendorInfo = new string[]
							{
								vendor.GetString(0, "billingname"),
								vendor.GetString(0, "billingstreet"),
								vendor.GetString(0, "billingzipcode"),
								vendor.GetString(0, "billingcity")
							};
			for (int i = 0; i < vendorInfo.Length; i++)
				addressTable.AddRow(cellProps, new string[] { vendorInfo[i] });

			pdfCanvas.WriteTable(addressTable);
		}
		
		private void WriteInvoiceProperties(PdfDocumentCanvas pdfCanvas, string factNr, int month, int year)
		{
			DateTime period = new DateTime(year, month, 1);

			PdfElementProperties properties = GetProps("invoiceinfotable");
			PdfTable invoiceInfo = new PdfTable(properties);
			Font headerFont = FontFactory.GetFont(properties.FontFamily, properties.FontSize, Font.BOLD);
			Font cellFont = FontFactory.GetFont(properties.FontFamily, properties.FontSize, properties.FontWeight);

			string[] content = new string[] { period.ToString("MMMM yyyy"), DateTime.Today.ToString("dd-MM-yyyy"), factNr, DateTime.Today.AddDays(14).ToString("dd-MM-yyyy") };
			PdfCellProperties headerProps = new PdfCellProperties { HorizontalAlignment = Element.ALIGN_RIGHT, PaddingBottom = 0f, Font = headerFont };
			PdfCellProperties contentProps = new PdfCellProperties { HorizontalAlignment = Element.ALIGN_RIGHT, PaddingBottom = 0f, Font = cellFont };
			invoiceInfo.AddRow(headerProps, properties.TableHeaders);
			invoiceInfo.AddRow(contentProps, content);

			pdfCanvas.WriteTable(invoiceInfo);
		}

		private void WriteInvoiceDetails(PdfDocumentCanvas pdfCanvas, ResultSet resultSet, InvoiceCalculator invoiceCalculator)
		{
			InvoiceDetailCollection invoiceLines = new InvoiceDetailCollection();

			PdfElementProperties properties = GetProps("invoicedetailstable");
			PdfTable invoiceDetailsTable = new PdfTable(properties);

			Font headerFont = FontFactory.GetFont(properties.FontFamily, properties.FontSize, Font.BOLD);
			Font cellFont = FontFactory.GetFont(properties.FontFamily, properties.FontSize, properties.FontWeight);
			BaseColor borderColor = new BaseColor(112, 173, 71);

			PdfCellProperties headerProps = new PdfCellProperties { HorizontalAlignment = Element.ALIGN_RIGHT, Border = Rectangle.TOP_BORDER | Rectangle.BOTTOM_BORDER, BorderColorTop = borderColor, BorderColorBottom = borderColor, BorderWidthBottom = 1.5f, BorderWidthTop = 1.5f, Font = headerFont };
			PdfCellProperties headerColumn1Props = new PdfCellProperties { PaddingLeft = 6f, Border = Rectangle.TOP_BORDER | Rectangle.BOTTOM_BORDER, BorderColorTop = borderColor, BorderColorBottom = borderColor, BorderWidthBottom = 1.5f, BorderWidthTop = 1.5f, Font = headerFont };
			PdfCellProperties[] allHeaderProps = new PdfCellProperties[properties.TableHeaders.Length];
			for (int i = 0; i < properties.TableHeaders.Length; i++)
				allHeaderProps[i] = (i == 1 ? headerColumn1Props : headerProps);

			invoiceDetailsTable.AddRow(allHeaderProps, properties.TableHeaders);

			PdfCellProperties lineProps = new PdfCellProperties { HorizontalAlignment = Element.ALIGN_RIGHT, BorderColorBottom = borderColor, Font = cellFont };
			PdfCellProperties lastLineProps = new PdfCellProperties { HorizontalAlignment = Element.ALIGN_RIGHT, PaddingBottom = 20f, BorderWidthBottom = 1.5f, BorderColorBottom = borderColor, Font = cellFont };

			foreach (InvoiceItem invoiceItem in invoiceCalculator.InvoiceItems)
			{
				invoiceLines.Add(new InvoiceDetailDeposits(invoiceItem.Deposit, invoiceItem.Percentage, invoiceItem.Fee));
			}
			if (invoiceCalculator.CorrectionFeeBelowMinimum > 0)
				invoiceLines.Add(new InvoiceDetailCorrection(invoiceCalculator.CorrectionFeeBelowMinimum));

			for (int i = 0; i < invoiceLines.Count; i++)
			{
				var invoiceLine = invoiceLines[i];

				PdfRow newRow = invoiceDetailsTable.NewRow((i == invoiceLines.Count - 1 ? lastLineProps : lineProps), invoiceLine.Values);
				newRow[1].HorizontalAlignment = Element.ALIGN_LEFT;
				invoiceDetailsTable.AddRow(newRow);
			}

			string[] texts = new string[] { invoiceCalculator.TotalDeposit.ToString("c2"), "", "Subtotaal:", invoiceCalculator.GrossFee.ToString("c2") };
			PdfCellProperties subTotalDefaulProps = new PdfCellProperties { HorizontalAlignment = Element.ALIGN_RIGHT, Font = cellFont };
			PdfCellProperties[] subTotalProps = new PdfCellProperties[]
				{
					new PdfCellProperties {HorizontalAlignment = Element.ALIGN_RIGHT, Font=headerFont},
					PdfCellProperties.defaultProperties,
					subTotalDefaulProps,
					subTotalDefaulProps
				};

			invoiceDetailsTable.AddRow(subTotalProps, texts);

			if (invoiceCalculator.TotalDiscount > 0)
			{
				string[] discountTexts = new string[] { "", "", "Korting:", (-invoiceCalculator.TotalDiscount).ToString("c2") };

				PdfCellProperties[] discountProps = new PdfCellProperties[]
				{
					PdfCellProperties.defaultProperties,
					PdfCellProperties.defaultProperties,
					new PdfCellProperties {HorizontalAlignment = Element.ALIGN_RIGHT, BorderColorBottom = borderColor, BorderWidthBottom = 1f, Font=cellFont},
					new PdfCellProperties {HorizontalAlignment = Element.ALIGN_RIGHT, BorderColorBottom = borderColor, BorderWidthBottom = 1f, Font=cellFont, PaddingBottom = 8f}
				};

				invoiceDetailsTable.AddRow(discountProps, discountTexts);

				string[] totalNoVatTexts = new string[] { "", "", "Subtotaal:", invoiceCalculator.NetFee.ToString("c2") };

				invoiceDetailsTable.AddRow(subTotalProps, totalNoVatTexts);
			}

			texts = new string[] { "", "", "BTW 21%", invoiceCalculator.TotalVat.ToString("c2") };
			PdfCellProperties[] vatProps = new PdfCellProperties[]
				{
					PdfCellProperties.defaultProperties,
					PdfCellProperties.defaultProperties,
					new PdfCellProperties {HorizontalAlignment = Element.ALIGN_RIGHT, BorderColorBottom = borderColor, BorderWidthBottom = 3f, Font=cellFont},
					new PdfCellProperties {HorizontalAlignment = Element.ALIGN_RIGHT, BorderColorBottom = borderColor, BorderWidthBottom = 3f, Font=cellFont, PaddingBottom = 8f}
				};

			invoiceDetailsTable.AddRow(vatProps, texts);

			decimal total = invoiceCalculator.TotalVat + invoiceCalculator.NetFee;
			texts = new string[] { "", "", "* Totaal", total.ToString("c2") };
			PdfCellProperties totalDefaulProps = new PdfCellProperties { HorizontalAlignment = Element.ALIGN_RIGHT, Font = headerFont };

			invoiceDetailsTable.AddRow(totalDefaulProps, texts);

			properties = GetProps("noneedtopayremark");
			cellFont = FontFactory.GetFont(properties.FontFamily, properties.FontSize, properties.FontWeight);
			PdfCellProperties remarkProps = new PdfCellProperties { HorizontalAlignment = Element.ALIGN_RIGHT, Colspan = 4, PaddingTop = 12f, Font = cellFont };
			invoiceDetailsTable.AddRow(remarkProps, new string[] {properties.Text} ); 

			pdfCanvas.WriteTable(invoiceDetailsTable);
		}

		private PdfElementProperties GetProps(string name)
		{
			TaskItemProperties properties;
			if (taskProperties.TryGetValue(name, out properties) == false)
				return null;

			string dummy;
			float fontSize = 8;
			float x = 0;
			float y = 0;
			int fontWeight = 0;
			int alignment = 0;
			string fontFamily = "Verdana";
			float tableWidth = 592;
			string text = "";

			if (properties.TryGetValue("text", out dummy))
				text = dummy;

			if (properties.TryGetValue("fontsize", out dummy))
				fontSize = (float)Convert.ToDouble(dummy);

			if (properties.TryGetValue("x", out dummy))
				x = (float)Convert.ToDouble(dummy);

			if (properties.TryGetValue("y", out dummy))
				y = (float)Convert.ToDouble(dummy);

			if (properties.TryGetValue("fontweight", out dummy))
			{
				switch (dummy)
				{
					case "bold": fontWeight = Font.BOLD; break;
					case "italic": fontWeight = Font.ITALIC; break;
					case "bolditalic": fontWeight = Font.BOLDITALIC; break;
				}
			}

			if (properties.TryGetValue("alignment", out dummy))
			{
				switch (dummy)
				{
					case "right": alignment = Element.ALIGN_RIGHT; break;
					case "center": alignment = Element.ALIGN_CENTER; break;
				}
			}

			if (properties.TryGetValue("fontfamily", out dummy))
				fontFamily = dummy;

			string[] headers = null;
			if (properties.TryGetValue("headers", out dummy))
				headers = dummy.Split('|');

			string[] scellWidths = null;
			float[] cellWidths = null;
			if (properties.TryGetValue("cellwidths", out dummy))
			{
				scellWidths = dummy.Split('|');
				cellWidths = new float[scellWidths.Length];
				tableWidth = 0;
				for (int i = 0; i < scellWidths.Length; i++)
				{
					cellWidths[i] = (float)XmlConvert.ToDouble(scellWidths[i]);
					tableWidth += cellWidths[i];
				}
			}

			if (properties.TryGetValue("tablewidth", out dummy))
				tableWidth = (float)Convert.ToDouble(dummy);

			return new PdfElementProperties { X = x, Y = y, Alignment = alignment, FontSize = fontSize, FontWeight = fontWeight, TableHeaders = headers, FontFamily = fontFamily, TableWidth = tableWidth, CellWidths = cellWidths, Text = text };
		}
	}
}
